# frozen_string_literal: true

module EE
  module Gitlab
    module BackgroundMigration
      # This migration removes all vulnerability_finding_signatures per project finding and re-inserts
      # the matching signature as provided by `vulnerability_finding.raw_metadata`
      module RecalculateVulnerabilityFindingSignaturesForFindings
        extend ::Gitlab::Utils::Override

        ALGORITHM_TYPES = { hash: 1, location: 2, scope_offset: 3 }.with_indifferent_access.freeze
        SAST_REPORT_TYPE = 0
        BATCH_SIZE = 1000

        class Vulnerabilities::Finding < ApplicationRecord
          self.table_name = 'vulnerability_occurrences'
        end

        class Vulnerabilities::FindingSignature < ApplicationRecord
          include ::EachBatch

          self.table_name = 'vulnerability_finding_signatures'

          belongs_to :finding, foreign_key: 'finding_id', class_name: 'Vulnerabilities::Finding'
        end

        override :perform
        def perform(start_id, stop_id)
          Vulnerabilities::FindingSignature.joins(:finding).where(id: start_id..stop_id).each_batch(of: BATCH_SIZE) do |signatures|
            now = Time.now
            rows = signatures.map(&:finding).map { |occurrence| build_row(occurrence, now) }.compact

            signatures.delete_all

            ApplicationRecord.legacy_bulk_insert(:vulnerability_finding_signatures, rows) # rubocop:disable Gitlab/BulkInsert

            mark_job_as_succeeded(start_id, stop_id)
          end
        rescue StandardError => e
          logger.error(
            message: "repopulate_vulnerability_finding_signatures failed for range #{start_id} to #{stop_id}",
            error: e.message
          )
        end

        private

        def build_row(finding, now)
          json = ::Gitlab::Json.parse(finding.raw_metadata)
          signature = json.dig('tracking', 'items').first&.dig('signatures')&.first
          bytea = ActiveRecord::Base.connection.escape_bytea(
            Digest::SHA1.digest(signature.fetch('value'))
          )

          {
            finding_id: finding.id,
            algorithm_type: ALGORITHM_TYPES[signature.fetch('algorithm')],
            signature_sha: bytea,
            created_at: now,
            updated_at: now
          }
        rescue JSON::ParserError => e
          # JSON extraction failed, return nil and skip insert of row
          logger.error(
            message: "repopulate_vulnerability_finding_signatures malformed json for #{finding.id}",
            error: e.message
          )
          nil
        rescue StandardError
          # JSON extraction failed, return nil and skip insert of row
          nil
        end

        def mark_job_as_succeeded(*arguments)
          ::Gitlab::Database::BackgroundMigrationJob.mark_all_as_succeeded(
            self.class.name.demodulize,
            arguments
          )
        end

        def logger
          @logger ||= ::Gitlab::BackgroundMigration::Logger.build
        end
      end
    end
  end
end
